﻿using PoemGame.Domain.Exceptions;
using PoemGame.Domain.GameAggregate.Events;
using PoemGame.Domain.PlayerAggregate;
using PoemGame.Domain.Seedwork;
using PoemGame.Domain.Services;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Numerics;
using System.Text;
using System.Threading.Tasks;

namespace PoemGame.Domain.GameAggregate
{
    public class Game : AggregateRoot
    {
        /// <summary>
        /// 游戏中的玩家
        /// </summary>
        public IReadOnlyList<PlayerInGame> PlayersInGame { get { return _players; } private set { _players.AddRange(value); } }

        public IReadOnlyList<PlayRecord> PlayRecords
        {
            get { return Records; }
        }
        private List<PlayerInGame> _players { get; set; } = new List<PlayerInGame>();

        protected List<PlayRecord> Records { get; set; } = new List<PlayRecord>();

        public GameStatus Status { get; private set; }

        public string GameCondition { get; private set; } = "";

        public string Description { get; private set; } = "";

        public GameType GameType { get; private set; }

        /// <summary>
        /// 创建时间
        /// </summary>
        public DateTime? GameCreateTime { get; private set; }
        /// <summary>
        /// 开始时间
        /// </summary>
        public DateTime? GameStartTime { get; private set; }
        /// <summary>
        /// 结束时间
        /// </summary>
        public DateTime? GameEndTime { get; private set; }

        /// <summary>
        /// 作答类型
        /// </summary>
        public PlayType PlayType { get; private set; }

        private Game()
        {

        }

        /// <summary>
        /// 游戏构造函数，在创建前需要使用IsValidateGame检查游戏是否合格
        /// </summary>
        /// <param name="gameType"></param>
        /// <param name="playType"></param>
        /// <param name="description"></param>
        /// <param name="gameCondition"></param>
        public Game(GameType gameType, PlayType playType,string description, string gameCondition)
        {
            Id = Guid.NewGuid();
            GameCondition = gameCondition;
            GameType = gameType;
            Status = GameStatus.Ready;
            Description = description;
            PlayType = playType;
        }

        /// <summary>
        /// 游戏构造函数，在创建前需要使用IsValidateGame检查游戏是否合格
        /// </summary>
        /// <param name="id"></param>
        /// <param name="gameType"></param>
        /// <param name="playtype"></param>
        /// <param name="gameCondition"></param>
        public Game(Guid id, GameType gameType,PlayType playtype, string description, string gameCondition)
        {
            Id = id;
            GameCondition = gameCondition;
            GameType = gameType;
            Status = GameStatus.Ready;
            Description = description;
            PlayType = playtype;
        }

        public async static Task<bool> ValidateGame(GameType gameType,
            string gameCondition,
            IDomainServiceFactory<ICheckGameConditionService> factory)
        {
            var service=factory.GetService(gameType);
            return await service.CheckGameCondition(gameCondition);
        }
        /// <summary>
        /// 玩家加入游戏
        /// </summary>
        /// <param name="player"></param>
        /// <returns></returns>
        public GeneralResult PlayerJoinGame(Player player)
        {
            var res = new GeneralResult();
            var p = _players.Find(o => o.PlayerId == player.Id);
            if (p != null)
            {
                res.IsSuccess = false;
                res.Message = "已经加入游戏";
            }
            else
            {
                //这里不需要给主键赋值
                var playeringame = new PlayerInGame(this.Id, player.Id, player.UserName, false, PlayersInGame.Count + 1);
                
                _players.Add(playeringame);
                res.IsSuccess = true;
                res.Message = $"{player.UserName}加入游戏";
                AddPlayerJoinGameEvent(player);

            }
            return res;
        }



        /// <summary>
        /// 玩家离开游戏
        /// </summary>
        /// <param name="player"></param>
        /// <returns></returns>
        public GeneralResult PlayerLeaveGame(Player player)
        {
            if (Status == GameStatus.Running)
            {
                return new GeneralResult
                {
                    IsSuccess = false,
                    Message = "已经开始，不能离开"
                };
            }

            if (Status == GameStatus.Done)
            {
                return new GeneralResult
                {
                    IsSuccess = false,
                    Message = "已经结束，不能离开"
                };
            }

            var obj = _players.Find(p => p.PlayerId == player.Id);
            if (obj != null)
            {
                if (obj.IsCreator)
                {
                    return new GeneralResult
                    {
                        IsSuccess = false,
                        Message = "创建者不能离开"
                    };
                }
                _players.Remove(obj);

                foreach (var item in PlayersInGame)
                {
                    item.Index = _players.IndexOf(item) + 1;
                }
                AddPlayerLeaveGameEvent(player);
                return new GeneralResult
                {
                    IsSuccess = true,
                    Message = $"{player.UserName}离开游戏"
                };
            }
            return new GeneralResult
            {
                IsSuccess = false,
                Message = "没有在游戏中"
            };
        }


        /// <summary>
        /// 获取创建者
        /// </summary>
        /// <returns>创建者</returns>
        public PlayerInGame? GetCreator()
        {
            return PlayersInGame.FirstOrDefault(o => o.IsCreator);
        }

        /// <summary>
        /// 获取等待玩家
        /// </summary>
        /// <returns></returns>
        public List<PlayerInGame> GetWaitingPlayers()
        {
            return _players.FindAll(x => x.PlayerStatus == PlayerGameStatus.Waiting).OrderBy(o => o.Index).ToList();
        }

        /// <summary>
        /// 获取当前玩家
        /// </summary>
        /// <returns></returns>
        public PlayerInGame GetCurrentPlayer()
        {
            return PlayersInGame.FirstOrDefault(o => o.PlayerStatus == PlayerGameStatus.Inturn)!;
        }

        /// <summary>
        /// 获取合格答案记录
        /// </summary>
        /// <returns></returns>
        public List<PlayRecord> GetProperAnswers()
        {
            return Records.FindAll(o => o.IsProperAnswer).OrderBy(o => o.PlayDateTime).ToList();
        }

        /// <summary>
        /// 设置游戏开始状态
        /// </summary>
        public void Start()
        {
            Status = GameStatus.Running;
            GameStartTime = DateTime.Now;
            SetFirstPlayer();
            AddGameStartEvent();
        }

        /// <summary>
        /// 设置游戏结束
        /// 游戏是否结束由客户端判断，比如可以强制终端
        /// </summary>
        /// <param name="winner">获胜者</param>
        public void GameOver(Player winner)
        {
            var playeringame = _players.FirstOrDefault(o => o.PlayerId == winner.Id);
            if (playeringame == null) throw new PlayerNotInGameException(winner.UserName + " not in game " + Id);

            playeringame.SetWinner();
            Status = GameStatus.Done;
            GameEndTime = DateTime.Now;

            AddGameFinishEvent(winner);
        }

        /// <summary>
        /// 游戏结束但没有赢家
        /// </summary>
        public void GameOver()
        {
            Status = GameStatus.DoneWithoutWinner;
            GameEndTime = DateTime.Now;
            AddGameFinishNoWinnerEvent();
        }
        /// <summary>
        /// 游戏暂停
        /// </summary>
        public void GamePasue()
        {
            Status = GameStatus.Pause;
        }
        /// <summary>
        /// 游戏进行
        /// </summary>
        /// <param name="player">进行游戏的玩家</param>
        /// <param name="answer">玩家的回答</param>
        /// <param name="checkAnswerServiceFactory">检查工厂</param>
        public async Task<bool> Play(Player player, string answer, IDomainServiceFactory<ICheckAnswerService> checkAnswerServiceFactory)
        {
            var checkAnswerService = checkAnswerServiceFactory.GetService(GameType);
            CheckAnswerServiceInput checkinput = GetCheckAnswerServiceInput(this);
            bool isProper = await checkAnswerService.CheckAnswer(checkinput, answer);
            AddPlayRecord(player, answer, DateTime.Now, isProper);
            AddPlayGameEvent(player,answer,isProper);
            return isProper;
        }

        private CheckAnswerServiceInput GetCheckAnswerServiceInput(Game game)
        {
            var res = new CheckAnswerServiceInput
            {
                GameCondition = game.GameCondition,
                MainType=game.GameType.MainType,
                SubType=game.GameType.SubType
            };
            foreach (var r in game.GetProperAnswers())
            {
                res.ProperAnswers.Add(r.Answer);
            }
            return res;
        }


        /// <summary>
        /// 设置回答用户
        /// </summary>
        /// <param name="playerid"></param>
        public void SetPlayerInturn(Guid playerid)
        {
            var playeringame = PlayersInGame.FirstOrDefault(o => o.PlayerId == playerid);
            if (playeringame != null && playeringame.PlayerStatus != PlayerGameStatus.Out)
            {
                var currentplayer = GetCurrentPlayer();
                if (currentplayer != null) currentplayer.PlayerStatus = PlayerGameStatus.Waiting;
                playeringame.PlayerStatus = PlayerGameStatus.Inturn;
            }
        }

        /// <summary>
        /// 设置用户出局，用于游戏中间强行退出，或者掉线等处理
        /// </summary>
        /// <param name="playerid"></param>
        public void SetPlayerOut(Guid playerid)
        {
            var playeringame = PlayersInGame.FirstOrDefault(o => o.PlayerId == playerid);
            if (playeringame != null && playeringame.PlayerStatus != PlayerGameStatus.Out)
            {
                if (PlayType == PlayType.Inturn)
                {
                    var current = GetCurrentPlayer();
                    if (current.PlayerId == playerid)
                    {
                        var n = GetNextPlayer(playerid);
                        if (n != null) SetPlayerInturn(n.PlayerId);
                    }
                }
                playeringame.PlayerStatus = PlayerGameStatus.Out;
                //如果没有玩家，则游戏结束
                if (GetCurrentPlayer() == null && GetWaitingPlayers().Count == 0)
                    GameOver();
            }
        }

        /// <summary>
        /// 获取指定用户的下一个游戏中用户（如果用户出局，不在此列）
        /// </summary>
        /// <param name="playerid"></param>
        /// <returns></returns>
        public PlayerInGame? GetNextPlayer(Guid playerid)
        {
            var playeringame = PlayersInGame.FirstOrDefault(o => o.PlayerId == playerid);
            if (playeringame != null)
            {
                var activateplayers = GetWaitingPlayers();
                var nextplayer = activateplayers.FirstOrDefault(o => o.Index > playeringame.Index);
                if (nextplayer == null && activateplayers.Count > 0) nextplayer = activateplayers[0];
                return nextplayer;
            }
            return null;
        }

        public void CreatorJoinGame(Player player)
        {
            if (PlayersInGame.Count > 0) throw new Exception("游戏中已经存在玩家");

            var playeringame = new PlayerInGame(Id, player.Id, player.UserName, true, 1);

            _players.Add(playeringame);
        }

        #region private



        private void AddPlayRecord(Player player, string answer, DateTime datetime, bool isproperanswer)
        {
            var playeringame = PlayersInGame.FirstOrDefault(o => o.PlayerId == player.Id);
            if (playeringame == null) throw new PlayerNotInGameException(player.UserName + " not in game " + Id);
            Records.Add(new PlayRecord(this.Id, player.Id, player.UserName, datetime, answer, isproperanswer));
        }

        private void SetFirstPlayer()
        {
            if (PlayType == PlayType.Inturn)
            {
                if (PlayersInGame.Count == 1)
                    PlayersInGame[0].PlayerStatus = PlayerGameStatus.Inturn;
                else
                {
                    var playeringame = PlayersInGame.FirstOrDefault(o => o.IsCreator);
                    if (playeringame != null)
                    {
                        playeringame.PlayerStatus = PlayerGameStatus.Waiting;
                        var nextplayer = PlayersInGame.FirstOrDefault(o => o.Index > playeringame.Index);
                        if (nextplayer != null)
                            nextplayer.PlayerStatus = PlayerGameStatus.Inturn;
                    }
                    else
                    {
                        PlayersInGame[0].PlayerStatus = PlayerGameStatus.Inturn;
                    }
                }

            }
        }
        #endregion

        #region Events
        /// <summary>
        /// 增加游戏开始事件
        /// </summary>
        private void AddGameStartEvent()
        {
            AddLocalEvent(new EventRecordLocal(new GameStartEventDataLocal(this, DateTime.Now),
                GetLocalDomainEvents().Count));
            AddOutBoundEvent(new EventRecordOutBound(new GameStartEventDataOutBound(Id, Description, DateTime.Now),
                GetOutBoundDomainEvents().Count));
        }

        /// <summary>
        /// 增加玩家加入游戏事件
        /// </summary>
        /// <param name="player"></param>
        private void AddPlayerJoinGameEvent(Player player)
        {
            AddLocalEvent(new EventRecordLocal(
                new PlayerJoinGameEventDataLocal(this, player.Id, player.UserName, DateTime.Now),
                GetLocalDomainEvents().Count));
            AddOutBoundEvent(new EventRecordOutBound(
                new PlayerJoinGameEventDataOutBound(Id, Description, player.Id, player.UserName, DateTime.Now),
                GetOutBoundDomainEvents().Count));
        }

        /// <summary>
        /// 增加玩家离开游戏事件
        /// </summary>
        /// <param name="player"></param>
        private void AddPlayerLeaveGameEvent(Player player)
        {
            AddLocalEvent(new EventRecordLocal(
                new PlayerLeaveGameEventDataLocal(this, player.Id, player.UserName, DateTime.Now),
                GetLocalDomainEvents().Count));
            AddOutBoundEvent(new EventRecordOutBound(
                new PlayerLeaveGameEventDataOutBound(Id, Description, player.Id, player.UserName, DateTime.Now),
                GetOutBoundDomainEvents().Count));
        }

        /// <summary>
        /// 增加游戏进行事件
        /// </summary>
        /// <param name="player">玩家</param>
        /// <param name="answer">作答</param>
        /// <param name="isProper">作答是否合适</param>
        private void AddPlayGameEvent(Player player, string answer, bool isProper)
        {
            AddLocalEvent(new EventRecordLocal(
                new GamePlayEventDataLocal(this, player.UserName, player.Id, answer, isProper, DateTime.Now),
                GetLocalDomainEvents().Count));
            AddOutBoundEvent(new EventRecordOutBound(
                new GamePlayEventDataOutBound(Id, Description, player.UserName, player.Id, answer, isProper,
                    DateTime.Now), GetOutBoundDomainEvents().Count));
        }

        /// <summary>
        /// 增加游戏结束事件
        /// </summary>
        /// <param name="player">游戏赢家</param>
        private void AddGameFinishEvent(Player player)
        {
            AddLocalEvent(
                new EventRecordLocal(
                    new GameFinishEventDataLocal(this, true, player.UserName, player.Id, DateTime.Now)
                    , GetLocalDomainEvents().Count));


            AddOutBoundEvent(
                new EventRecordOutBound(
                    new GameFinishEventDataOutBound(this.Id,this.Description,true,player.UserName,player.Id,DateTime.Now)
                    , GetOutBoundDomainEvents().Count));
        }
        /// <summary>
        /// 增加没有赢家的游戏结束事件
        /// </summary>
        private void AddGameFinishNoWinnerEvent()
        {
            AddLocalEvent(
                new EventRecordLocal(
                    new GameFinishEventDataLocal(this, false, "", Guid.Empty, DateTime.Now)
                    , GetLocalDomainEvents().Count));
            AddOutBoundEvent(
                new EventRecordOutBound(
                    new GameFinishEventDataOutBound(Id, Description, false, "", Guid.Empty, DateTime.Now)
                    , GetOutBoundDomainEvents().Count));
        }
        
        

        #endregion
    }
}
